package com.study.spaoplog.aop;

import com.study.spaoplog.anno.NeedLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author study
 * @version 1.0
 * @date 2021/6/8 17:28
 * <p>使用@Aspect注解声明一个切面</p>
 */
@Slf4j
// @Aspect
@Order(0)  // 值越小，优先级越高
// @Component
public class LockAspect {
    private final ReentrantLock lock = new ReentrantLock();

    /**
     * 切入点表达式|注解
     */
    @Pointcut("@annotation(com.study.spaoplog.anno.NeedLock)")
    public void executionService() {

    }

    @Around("executionService()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //执行目标函数
        Object obj = joinPoint.proceed();
        // System.out.println("LockAspect aop之后的对象-"+joinPoint.getTarget());
        NeedLock lock = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(NeedLock.class);

        System.out.println(StringUtils.center("打印参数", 51, "*"));
        System.out.println(getKey(joinPoint, lock.key()));
        System.out.println(StringUtils.center("-", 51, "*"));
        return obj;
    }

    /**
     * https://www.cnblogs.com/wangshen31/p/9379197.html
     * <b>除了注解@Around的方法外，其他都可以加这个JoinPoint作参数。@Around注解的方法的参数一定要是ProceedingJoinPoint</b>
     */
    // @Around("executionService()")
    public Object aroundWithLock(ProceedingJoinPoint joinPoint) throws Throwable {
        //执行目标函数
        lock.lock();
        try {
            Object obj = joinPoint.proceed();
            return obj;
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 根据参数 和注解 获取 redis key值
     *
     * @param joinPoint
     * @param key
     * @return
     */
    public String getKey(ProceedingJoinPoint joinPoint, String key) {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        final StringBuffer buffer = new StringBuffer();
        buffer.append(className).append("_").append(methodName);
        try {
            if ("".equals(key)) {
                //默认类名 + 方法名

                return buffer.toString();
            }
            //TODO 这一段写的真好，看看spring缓存哪里怎么设置的，好像是 springcache有
            if (key.startsWith("#args")) {
                //包含 #args 读取参数 设置key 不包含直接返回
                //获取参数
                Object[] args = joinPoint.getArgs();
                //获取注解下标  例如：#args[0]  或者 #args[1].getName()
                Integer index = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));
                Object keyArgs = args[index];
                if (key.split("\\.").length <= 1) {
                    buffer.append(keyArgs.toString());
                    return buffer.toString();
                }
                //反射执行方法 拿到返回值 返回key
                Class clas = keyArgs.getClass();
                Method method = clas.getMethod(key.split("\\.")[1].split("\\(")[0]);
                buffer.append(method.invoke(keyArgs).toString());
                return buffer.toString();
            }
            buffer.append(key);
            return buffer.toString();
        } catch (Exception e) {
            return buffer.toString();
        }
    }
	/*
    Redis分布式锁，使用aop +自定义注解 实现
    版权声明：本文为CSDN博主「农夫YH」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。
    原文链接：https://blog.csdn.net/ChenMMo/article/details/106891626
    */
}
